﻿
/*
Text Parser of Supertree/TextEditor, v1.1

Author: Feng Weiguo ( 冯卫国 )
Mail: forxm@21cn.com
Web: www.supertree.org
2008-1

All Rights Reserved, 2008-9

LICENSE: LGPL

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License (LGPL) as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.
*/

$esoe.$preload(
	"org.supertree.editor.TextParser",
	function(sVarName)
	{
		if( sVarName ) this.sVarName= sVarName;
	},
	function()
	{
		this.prototype=
		{
			sVarName: "",
			
			sFrameID: "",
			
			sStackSplit: ",",
			sBlockSpan1: (window.ActiveXObject)?"<SPAN class=":"<span class=\"",
			sBlockSpan2: (window.ActiveXObject)?">":"\">",
			sBlockSpan3: (window.ActiveXObject)?"</SPAN>":"</span>",
			
			bLineNumber:0,
			bReadOnly:0,
			
			ruleDefault: [/($)/g ,["","",0]],	//plain text
			ruleTop: null,
			
			objTop: null,
			
			regAmp: /\&/g,
			regLt: /</g,
			regGt: />/g,
			regSp: /\s/g,
			
			FormatText: function(s,sColor)
			{
				if(sColor)
				{
					return this.sBlockSpan1+sColor+this.sBlockSpan2+
							s.replace( this.regAmp , "&amp;")
							.replace( this.regLt , "&lt;").replace( this.regGt , "&gt;")
							.replace( this.regSp ,"&nbsp;" )
							+this.sBlockSpan3;
				}
				else
				{
					return	s.replace( this.regAmp , "&amp;")
							.replace( this.regLt , "&lt;").replace( this.regGt , "&gt;")
							.replace( this.regSp ,"&nbsp;" )
							;
				}
			},
			
			Parse: function( obj, aRule, aRuleIndex )
			{
				var sa=new Array;
				var rule=aRule[aRule.length-1];
				var sParam=aRuleIndex[aRuleIndex.length-1];
				sParam= ( sParam.charAt(0)=="s" )?sParam.substring(1,sParam.length):"";
				
				var str=window.ActiveXObject?obj.innerText:obj.textContent.replace( this.regDelR, "" ).replace( this.regSp, " " );
				var i=0;
				var imax=str.length;
				
				//if(imax>2000) this.TRACE("Parse:"+imax);
				
				var er,j,jmax,ri,k;
				
				var bErr=0;
				var imax2k= (imax<2000)?imax:2000;
				
				while(i<imax2k)
				{
					if( sParam && sParam==str.substring(i,i+sParam.length) )
					{
						j=rule.length-1;
						ri= rule[j];
						er=[sParam];
						
						if( ri[0]!="void" )
						{
							if(ri[0]=="err") bErr=1;
							
							sa.push( this.FormatText(sParam,ri[0]) );
							i+=sParam.length;
						}
						else if( ri[2]<0 && aRule.length<2 )	// avoid invalid pop back and loop
						{
							sa.push( this.FormatText(sParam,"err") );
							bErr=1;
							i+=sParam.length;
						}
					}
					else
					{
						rule[0].lastIndex=i;
						er= rule[0].exec( str );
					
						//matched
						jmax=er.length;
						for(j=1;j<jmax;j++) if( er[j] ) break;		//get matched sequence
						if(j==jmax)j=jmax-1;
						
						ri= rule[j];
						
						if( er.index!=i )
						{
							if(ri[0]=="err"||ri[1]=="err") bErr=1;
							//text before match
							sa.push( this.FormatText(str.substring(i,er.index),ri[1]) );
						}
						else if(ri[0]=="err" || ri[1]=="errx") bErr=1;
						
						//matched text
						i=er.index;
						if(er[0])
						{
							if( ri[0]!="void" )
							{
								sa.push( this.FormatText(er[0],ri[0]) );
								i+=er[0].length;		//don't use er.lastIndex, un-readable in ff 2.0
							}
							else if( ri[2]<0 && aRule.length<2 )	// avoid invalid pop back and loop
							{
								sa.push( this.FormatText(er[0],"err") );
								i+=er[0].length;
								bErr=1;
							}
						}
					}
					
					k=ri[2];
					if(k==1)
					{
						//push
						aRule[aRule.length]=ri[4];	//don't use push(), for ri[4] is array
						
						aRuleIndex.push(j.toString());
						if(ri[3])
						{
							sParam= ri[3].replace( "$1", er[0] ).replace( "$P", sParam );
							aRuleIndex.push( "s"+sParam );
						}
						else sParam="";

						rule=ri[4];
					}
					else if(k<0 )
					{
						do
						{
							//pop
							if( aRule.length>1 )
							{
								aRule.pop();
								if( aRuleIndex.pop().charAt(0)=="s") aRuleIndex.pop();
							}
							else break;
							
							k+=1;
						} while(k<0);
						
						if(i<imax)
						{
							rule=aRule[aRule.length-1];
							sParam=aRuleIndex[aRuleIndex.length-1];
							sParam= ( sParam.charAt(0)=="s" )?sParam.substring(1,sParam.length):"";
						}
					}
				}
				if( i<imax )
				{
					sa.push( this.FormatText(str.substring(i,str.length)) );
				}
				
				obj.setAttribute("Stack",aRuleIndex.join(this.sStackSplit));
				
				var sAll= (sa.length>0)?sa.join(""):"";
				
				if(obj==this.objTop.lastChild)
				{
					obj.style.borderBottom="";
					//this.TRACE(bErr);
					this.CheckLast(bErr);	//ie6 bug
				}
				else
				{
					//this.TRACE( bErr+":"+sAll );
					obj.style.borderBottom=bErr?"1px solid red":"";
				}
				
				if( obj.innerHTML!= sAll )
				{
					//alert( obj.innerHTML +"\n==\n"+sAll );
					
					if( sAll=="" )	//for firefox
					{
						if(!window.ActiveXObject)
						{
							if( obj.innerHTML=="<br>" ) return false;
							else sAll="<br>";
						}
					}
					
					obj.innerHTML= sAll;
					return true;
				}
				else if( sAll=="" )
				{
					if(!window.ActiveXObject)
					{
						//for firefox
						obj.innerHTML= "<br>";
						return true;
					}
					return false;
				}
				else return false;
			},
			
			//expand tab to 4 spaces
			ExpandTab: function( s, n )
			{
				var a=s.split("\t");
				var j,jmax;
				jmax=a.length-1;
				for(j=0;j<jmax;j++)
				{
					n+=a[j].length;
					switch(n%4)
					{
						case 0: a[j]+="    "; n+=4; break;
						case 1: a[j]+="   "; n+=3; break;
						case 2: a[j]+="  "; n+=2; break;
						case 3: a[j]+=" "; n+=1; break;
					}
				}
				return a.join("");
			},
			
			/*
			//expand tab to 8 spaces
			ExpandTab8: function( s, n )
			{
				var a=s.split("\t");
				var j,jmax;
				jmax=a.length-1;
				for(j=0;j<jmax;j++)
				{
					n+=a[j].length;
					switch(n%8)
					{
						case 0: a[j]+="        "; n+=8; break;
						case 1: a[j]+="       "; n+=7; break;
						case 2: a[j]+="      "; n+=6; break;
						case 3: a[j]+="     "; n+=5; break;
						case 4: a[j]+="    "; n+=4; break;
						case 5: a[j]+="   "; n+=3; break;
						case 6: a[j]+="  "; n+=2; break;
						case 7: a[j]+=" "; n+=1; break;
					}
				}
				return a.join("");
			},
			*/
			
			nMaxLine: 0,
			
			UpdateLineNumber: function(bChange)
			{
				var m;
				if( this.bLineNumber )
				{
					m=this.objTop.childNodes.length;
					if(m<10)m=99;
					
					if( m>this.nMaxLine )
					{
						m= m.toString().replace(/\d/g, "9" );
						this.nMaxLine=parseInt(m);
						m= (window.ActiveXObject?2.5:0) + (m.length-2)*0.5;
						//if(m<0.5) m=0.5;
						this.objTop.style.marginLeft=m+"em";
					}
				}
				else
				{
					if( this.nMaxLine || bChange)
					{
						m=0.5;
						this.nMaxLine=0;
						this.objTop.style.marginLeft=m+"em";
						//alert("h");
					}
				}
				if(bChange)
				{
					this.objTop.style.listStyleType=this.bLineNumber?"decimal":"none";
					this.objTop.style.paddingLeft=this.bLineNumber?"":"0px";
				}
			},
			
			regDelR: /\r/g ,
			
			getWindow: function()
			{
				return this.sFrameID?document.getElementById(this.sFrameID).contentWindow:window;
			},
			getDocument: function()
			{
				return this.sFrameID?document.getElementById(this.sFrameID).contentWindow.document:document;
			},
			
			getInnerText: function(obj)
			{
				if(!obj) alert(this.getInnerText.caller.caller);
				
				return window.ActiveXObject?obj.innerText:obj.textContent.replace( this.regDelR, "" ).replace( this.regSp, " " );
			},

			setInnerText: function(obj,s)
			{
				if( window.ActiveXObject ) obj.innerText=s;
				else obj.textContent=s;
			},
			
			OnInitViewer: function( objTop )
			{
				if(this.sFrameID) this.objTop= this.getDocument().body.childNodes[0];
				else this.objTop= objTop;
				
				if( this.sDelaySetText!==null )
				{
					this.SetText( this.sDelaySetText );
					this.sDelaySetText=null;
				}
				
				this.UpdateLine( this.objTop.childNodes[0] );
			},

			InitViewer: function( objContainer, bLineNumber, bReadOnly, bWrap, ruleTop  )
			{
				if( ruleTop )
				{
					this.ruleTop= ruleTop;
				}
				
				this.bLineNumber=bLineNumber?1:0;
				this.bReadOnly=bReadOnly?1:0;
				
				// check editable container
				if(!bReadOnly)
				{
					if( objContainer.tagName.toUpperCase()!="IFRAME" )
					{
						alert("error: editable container must be an <IFRAME>");
						bReadOnly=1;
					}
				}
				
				this.nMaxLine=-1;
				var sOLStyle="style=\""+(bLineNumber?"":"list-style:none;padding-left:0px;margin-left:0.5em;")+(bWrap?"*word-wrap:break-word;":"white-space:nowrap;")+"margin-top:0px;margin-bottom:0px;font-family:'宋体','Courier New';font-size:16px;letter-spacing:1px;\"";
				
				var sa=new Array;
				
				if(bReadOnly && objContainer.tagName.toUpperCase()!="IFRAME" )
				{
					this.GetText(sa);
					objContainer.innerHTML=	"<ol "+sOLStyle+"><li></li></ol>";
					this.OnInitViewer( objContainer.childNodes[0] );
					if( sa.length>0 )
					{
						//alert("|"+sa.join("\n")+"|");
						this.SetText( sa.join("\n") );
					}
				}
				else
				{
					this.sFrameID= objContainer.id;
					if( ! this.sFrameID )
					{
						objContainer.id= "frm"+(new Date()).getTime();
						this.sFrameID= objContainer.id;
					}
					
					var oDoc=objContainer.contentWindow.document;
					oDoc.open();
					oDoc.write(
						"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"+
						"<html xmlns=\"http://www.w3.org/1999/xhtml\">",
						"<head>"+
						"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"+
						"<style>.err{}</style>"+
						"</head>"+
						"<body style='margin:0px;'"+(this.bReadOnly?"":" contentEditable")+">"+
						"<ol "+sOLStyle+"><li></li></ol>"+
						"</body>"+
						"</html>"
						);
					oDoc.close();
					
					///objContainer.contentWindow.addEventListener('click',function(){alert('ppp');});
					
					var RC= $esoe.$declare( "org.supertree.editor.RuleCompiler" );
					RC.ColorStyle(RC.aColor,oDoc);
					
					if( window.ActiveXObject )
					{
						//if( this.OnInitEditor ) this.OnInitEditor();
						//else this.OnInitViewer();
						this.OnInitViewer();
					}
					else
					{
						//if( this.OnInitEditor ) window.setTimeout( this.sVarName+".OnInitEditor()",1);
						//else window.setTimeout( this.sVarName+".OnInitViewer()",1);
						//alert(this.sVarName);
						//window.setTimeout( this.sVarName+".OnInitViewer()",1);
						this.OnInitViewer();
					}
				}
			},
			
			aOp: null,
			nOpIdx: 0,

			_op_fail: function(sErr)
			{
				//this.aOp.splice(0,this.aOp.length);
				this.aOp.splice(0,this.nOpIdx+1);
				this.nOpIdx=0;
				alert("op fail"+(sErr?(": "+sErr):""));
			},

			_saveop: function(o)
			{
				if(this.nOpIdx<this.aOp.length) this.aOp.splice(this.nOpIdx,this.aOp.length-this.nOpIdx);
				
				if( o[0] && this.nOpIdx>0 )
				{
					var a= this.aOp[this.nOpIdx-1];
					
					if(!a)
					{
						alert( this.nOpIdx +"\n"+"["+this.aOp.join("]\n[")+"]" );

					}
					
					if( a[0]==o[0] )
					{
						switch(o[0])
						{
							case "Del":
								if( a[1]==o[1] && a[2]==o[2] && o[3].length==1 && !o[4] )
								{
									a[3]+=o[3];
									return;
								}
								break;
							case "BS":
								if( a[1]==o[1] && a[2]==(o[2]+1) && o[3].length==1 && !o[4] )
								{
									a[3]=o[3]+a[3];
									a[2]-=1;
									return;
								}
								break;
							case "Inp":
								if( a[1]==o[1] && (a[2]+a[4].length)==o[2] && ((a[3].length==0 && o[3].length==0)||(a[3].length>0 && o[3].length==1)) && a[4].length>0 && o[4].length>0 )
								{
									var ch1= a[4].charCodeAt(a[4].length-1);
									var ch2= o[4].charCodeAt(0);
									
									ch1=((47<ch1 && ch1<58) || (64<ch1 && ch1<91) || (96<ch1 && ch1<123))?1:0;
									ch2=((47<ch2 && ch2<58) || (64<ch2 && ch2<91) || (96<ch2 && ch2<123))?1:0;
									
									if( !(ch2==1 && ch1==0) )
									{
										a[3]+=o[3];
										a[4]+=o[4];
										return;
									}
								}
								break;
						}
					}
				}
				
				this.aOp[this.aOp.length]=o;
				this.nOpIdx++;
			},

			_comparestr: function(str1,str2)
			{
				if(window.ActiveXObject) return ( str1==str2 )?0:1;
				else return ( str1.replace(this.regSp," ")==str2.replace(this.regSp," ") )?0:1;
			},

			bModified: false,
			
			//return end [nLine,nCol], null if fail
			//nMode: 0/undefined:normal, 1:undo/redo/do_not_save, 2:passive changing, record step only
			_op: function(nLine,nCol,sDel,sIns,bRaw,nMode,sKey)
			{
				
				if( !sIns && !sDel ) return [nLine,nCol];	//void op
				
				if(nMode==2)
				{
					this._saveop([sKey,nLine,nCol,sDel,sIns]);
					return;
				}
				
				if( !this.bModified )
				{
					this.bModified= true;
					$esoe.$notify( this, "before-modify", null );
				}
				
				var a,obj,obji,objp,s,s2,i,imax,j,jmax;
				
				obj=this.objTop.childNodes[nLine];
				if(!obj){ this._op_fail("line fail"); return null; }
				s=this.getInnerText(obj);
				
				var oWin= this.getWindow();
				var oDoc= oWin.document;
				
				//delete
				if(sDel)
				{
					a=sDel.replace( /\r/g , "").split("\n");
					imax=a.length-1;
					if(imax>0)
					{
						//check
						//first
						if( this._comparestr( s.substring(nCol,s.length),a[0] )!=0 )
						{
							//alert( "["+s.substring(nCol,s.length)+"]\n["+a[0]+"]" );
							this._op_fail("not match(1)");
							return null;
						}
						
						//middle
						obji=obj;
						
						if(window.ActiveXObject)
						{
							for(i=1;i<imax;)
							{
								jmax=i+200; if(jmax>imax)jmax=imax;
								for(j=i;j<jmax;j++)
								{
									if( this.getInnerText(obji=obji.nextSibling)!=a[j] ) { this._op_fail("not match(3):"+j/*+"\n|"+a[j]+"|\n|"+this.getInnerText(obji)+"|"*/); return null; }
								}
								i=jmax; oWin.status="checking "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
							}
						}
						else
						{
							var regSp= this.regSp;
							for(i=1;i<imax;)
							{
								jmax=i+200; if(jmax>imax)jmax=imax;
								for(j=i;j<jmax;j++)
								{
									if( this.getInnerText(obji=obji.nextSibling).replace(regSp," ")!=a[j].replace(regSp," ") ) { this._op_fail("not match(4)"); return null; }
								}
								i=jmax; oWin.status="checking "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
							}
						}
						//last
						s2=this.getInnerText(obji.nextSibling);
						if( this._comparestr( s2.substring(0,a[i].length),a[i] )!=0 ) { this._op_fail("not match(5)"); return null; }
						
						//delete
						s=s.substring(0,nCol)+s2.substring(a[i].length,s2.length);
						objp=obj.parentNode;
						imax++;
						for(i=1;i<imax;)
						{
							jmax=i+200; if(jmax>imax)jmax=imax;
							for(j=i;j<jmax;j++) objp.removeChild(obj.nextSibling);
							i=jmax; oWin.status="deleting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
						}
						oWin.status="";
						if( window.ActiveXObject ) obj.innerText=s;
						else obj.textContent=s;
					}
					else
					{
						//check
						if(this._comparestr( s.substring(nCol,nCol+a[0].length),a[0] )!=0 ) { this._op_fail("not match(7)"); return null; }

						//delete
						s=s.substring(0,nCol)+s.substring(nCol+a[0].length,s.length);
						if( window.ActiveXObject ) obj.innerText=s;
						else obj.textContent=s;
					}
				}
				
				//insert
				if(sIns)
				{
					if( bRaw )
					{
						var ExpandTab=this.ExpandTab;

						a=sIns.replace( this.regDelR,"" ).replace( /\u00a0/," " ).split("\n");
						a[0]=ExpandTab(a[0],nCol);
						imax=a.length;
						for(i=1;i<imax;)
						{
							jmax=i+200; if(jmax>imax)jmax=imax;
							for(j=i;j<jmax;j++) a[j]=ExpandTab(a[j],0);
							i=jmax; oWin.status="normalizing "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
						}
						oWin.status="";
					}
					else
					{
						a=sIns.split("\n");
					}
					
					imax=a.length-1;
					if(imax>0)
					{
						//first
						this.setInnerText(obj,s.substring(0,nCol)+a[0]);
						//middle
						objp=obj.parentNode;
						obji=obj.nextSibling;
						if( window.ActiveXObject )
						{
							if( obji)
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++) objp.insertBefore( oDoc.createElement("li"),obji ).innerText=a[j];
									i=jmax; oWin.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
							else
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++) objp.appendChild( oDoc.createElement("li") ).innerText=a[j];
									i=jmax; oWin.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
						}
						else
						{
							if( obji)
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++) objp.insertBefore( oDoc.createElement("li"),obji ).textContent=a[j];
									i=jmax; oWin.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
							else
							{
								for(i=1;i<imax;)
								{
									jmax=i+200; if(jmax>imax)jmax=imax;
									for(j=i;j<jmax;j++) objp.appendChild( oDoc.createElement("li") ).textContent=a[j];
									i=jmax; oWin.status="inserting "+i+"/"+imax+ " ("+Math.round(i*100/imax)+"%)";
								}
							}
						}
						oWin.status="";
						//last
						s=a[i]+s.substring( nCol, s.length );
						if( obji) this.setInnerText( objp.insertBefore( oDoc.createElement("li"),obji ),s );
						else this.setInnerText( objp.appendChild( oDoc.createElement("li") ), s );
						
						if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,bRaw?a.join("\n"):sIns]);
						this.UpdateLineNumber();
						
						return [nLine+imax,a[i].length];
					}
					else
					{
						s=s.substring(0,nCol)+a[0]+s.substring(nCol,s.length);
						if( window.ActiveXObject ) obj.innerText=s;
						else obj.textContent=s;
						
						if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,bRaw?a.join("\n"):sIns]);
						this.UpdateLineNumber();
						
						return [nLine,nCol+a[0].length];
					}
				}
				else
				{
					if( !nMode ) this._saveop([sKey,nLine,nCol,sDel,""]);
					return [nLine,nCol];
				}
			},

			sDelaySetText: null,
			
			SetText: function( s )
			{
				this.aStkCache1=null;
				this.aStkCache2=null;
				if(this.aOp) this.aOp.splice(0,this.aOp.length);
				this.nOpIdx=0;
				if(this.aUpdateList) this.aUpdateList.splice(0,this.aUpdateList.length);
				this.nUpdateLine=0;

				
				//clear selection for editor
				this.selSave= null;

				if( this.objTop )
				{
					this.objTop.innerHTML="<li></li>";
					this._op(0,0,"",s,1,1);
					//alert(this.nOpIdx+","+this.aOp.length);
					this.UpdateLineNumber();
					this.UpdateLine(this.objTop.childNodes[0]);
				}
				else
				{
					this.sDelaySetText=s;
				}
			},
			
			GetText: function(sa)
			{
				if(!this.objTop) return;
				var obj= this.objTop.firstChild;
				if(window.ActiveXObject)
				{
					while(obj) { sa.push(obj.innerText); obj=obj.nextSibling; }
				}
				else
				{
					while(obj) { sa.push(obj.textContent.replace( this.regDelR, "" ).replace( this.regSp, " " )); obj=obj.nextSibling; }
				}
			},
			
			GetTextEx: function()
			{
				var sa=new Array;
				this.GetText(sa);
				return sa.join("\n");
			},
			
			aUpdateList: null,
			nUpdateLine: 0,		//for time out updating report, estimate
			
			aStkCache1: null,		//cache for current editing
			
			//include obj
			UpdateLine: function(obj)
			{
				if(!obj || obj.parentNode!=this.objTop ) return;
				
				if( !this.aStkCache1 ) this.aStkCache1=["",0,0];
				var stk=this.aStkCache1;
				this.GetHeadStack( stk, obj );
				var rule=[].concat(stk[1]);
				var ruleIndex=[].concat(stk[2]);
				
				var sStk=obj.getAttribute("Stack");
				this.Parse( obj, rule, ruleIndex );
				
				var tm= (new Date()).getTime();
				obj=this.UpdateGroup(rule,ruleIndex,obj,sStk);
				
				//add to update list, check invalid item
				if(obj)
				{
					tm= (new Date()).getTime()-tm;
					if( tm<1  ) tm=1;
					else if( tm>20 ) tm=20;
					
					if( !this.aUpdateList )
					{
						//void list, undefined
						this.aUpdateList=new Array;
						this.aUpdateList[0]=obj;
						this.nUpdateLine=0;
					}
					else
					{
						var a=this.aUpdateList;
						var i=a.length-1;
						if( i<0 ) { a[0]=obj; this.nUpdateLine=0; }	//void list
						else
						{
							var y0=obj.offsetTop;
							var yi;
							for(;i>=0;i--)
							{
								if(a[i].parentNode!=this.objTop ) { a.splice(i,1); continue; }
								
								yi=a[i].offsetTop;
								//alert(yi+":"+y0)
								if( yi==y0 ) break;
								if( yi<y0 ) continue;
								//insert
								//alert("h "+i+":"+(a.length-1));
								if( i==(a.length-1) ) { a.push(obj); this.nUpdateLine=0;}
								else a.splice( i+1,0,obj);
								break;
							}
							
							if( i<0 )
							{
								//insert
								if( a.length==0 ) { a[0]=obj; this.nUpdateLine=0; }
								else a.splice( 0,0,obj);
							}
						}
					}
					
					//this.SetUpdateTimeout(tm);
					window.setTimeout( this.sVarName+".TimeoutUpdate()", tm );
					
					//report
					var i= this.nUpdateLine;
					var imax=this.objTop.childNodes.length;
					(window.parent?window.parent:window).status="formatting ~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"% ) "+tm;
				}
			},
			
			aStkCache2: null,		//cache for timeout updating
			
			TimeoutUpdate: function()
			{
				
				var a= this.aUpdateList;
				if(!a) return;
				var i= a.length-1;
				if(i<0) return;
				
				var obj;
				while( (obj=a[i]).parentNode!=this.objTop )
				{
					a.pop();
					i--;
					if(i<0 )
					{
						(window.parent?window.parent:window).status="";
						return;
					}
				}
				
				if( !this.aStkCache2 ) this.aStkCache2=["",0,0];
				var stk=this.aStkCache2;
				this.GetHeadStack( stk, obj );
				var rule=stk[1];
				var ruleIndex=stk[2];
				var sStk=obj.getAttribute("Stack");
				this.Parse( obj, rule, ruleIndex );
				//alert( obj.innerHTML );
				
				var tm= (new Date()).getTime();
				obj= this.UpdateGroup(rule,ruleIndex,obj,sStk);
				
				if( obj )
				{
					tm= (new Date()).getTime()-tm;
					if( tm<1  ) tm=1;
					else if( tm>20 ) tm=20;
					
					stk[0]=obj.previousSibling.getAttribute("Stack");
					a[i]=obj;
					
					//this.SetUpdateTimeout(tm);
					window.setTimeout( this.sVarName+".TimeoutUpdate()", tm );
					
					//report
					this.nUpdateLine+=21;
					var i= this.nUpdateLine;
					var imax=this.objTop.childNodes.length;
					(window.parent?window.parent:window).status="formatting ~"+i+"/"+imax+ " (~"+Math.round(i*100/imax)+"%) "+tm;
					
				}
				else
				{
					stk[0]="";	//clear cache
					a.pop();
					if(a.length>0 )
					{
						//this.SetUpdateTimeout(15);
						window.setTimeout( this.sVarName+".TimeoutUpdate()", 10 )
					}
					else
					{
						(window.parent?window.parent:window).status="";
					}
				}
			},
			
			CheckLast: function(bErr)
			{
				var obj=(!this.bReadOnly && window.ActiveXObject)?this.objTop.parentNode:this.objTop;	//ie6 bug
				//this.TRACE(bErr +","+(!bErr));
				//obj.style.borderBottom=(bErr || this.objTop.lastChild.getAttribute("Stack")!="0")?"1px red solid":"none";
				obj.style.borderBottom=(bErr || this.ruleTop.sEndStack.indexOf("|"+this.objTop.lastChild.getAttribute("Stack")+"|")<0)?"1px red solid":"none";
			},

			TRACE: function(s)
			{
				window.status=(new Date).getTime()+" "+ s;
			},
			
			//start parse from obj.nextSibling
			UpdateGroup: function( rule, ruleIndex, obj, sOldStk )
			{
				var i,obji;
				for(i=0;i<20;i++)
				//for(i=0;i<5;i++)
				//for(i=0;i<2;i++)
				{
					if( sOldStk==obj.getAttribute("Stack") ) return null;
					if( !(obji=obj.nextSibling) ) { /*this.CheckLast();*/ return null; }		//last line

					sOldStk=(obj=obji).getAttribute("Stack");
					this.Parse( obj, rule, ruleIndex );
				}
				if( sOldStk==obj.getAttribute("Stack") ) return null;
				
				if( !(obji=obj.nextSibling) ) { /*this.CheckLast();*/ return null; }		//last line
				return obji;
			},
			
			//stkCache: stack cache, [str,rule,ruleIndex]
			GetHeadStack: function( stkCache, obj, bThis )
			{
				var str="0";
				
				if( obj )
				{
					if( bThis ) str= obj.getAttribute("Stack");
					else if( obj.previousSibling ) str= obj.previousSibling.getAttribute("Stack");
				}
				
				if(!str) str="0";
				
				//create stack from string
				if( str!=stkCache[0])
				{
					//alert("update cache");
					var a1= [this.ruleTop];
					var a2= str.split(this.sStackSplit);
					var i,imax,idx,a=a1[0];
					imax=a2.length;
					for(i=1;i<imax;i++)
					{
						if( a2[i].charAt(0)=='s' ) continue;
						
						a=a[a2[i]][4];
						a1[a1.length]=a;
					}
					
					stkCache[0]=str;
					stkCache[1]=a1;
					stkCache[2]=a2;
				}
			},
			
			SetFrameID: function( sFrameID )
			{
				this.sFrameID= sFrameID;
				this.TimeoutUpdate();
			}
		}

		this.prototype.ruleDefault.sEndStack="|0|";
		this.prototype.ruleTop= this.prototype.ruleDefault;
		
	}
	);
